/*
 *
 * Copyright 2021-2025 Software Radio Systems Limited
 *
 * This file is part of srsRAN.
 *
 * srsRAN is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * srsRAN is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * A copy of the GNU Affero General Public License can be found in
 * the LICENSE file in the top-level directory of this distribution
 * and at http://www.gnu.org/licenses/.
 *
 */

#include "srsran/ran/pdcch/dci_packing.h"
#include "srsran/adt/interval.h"
#include "srsran/adt/span.h"
#include "srsran/ran/pdcch/dci_packing_formatters.h"
#include "srsran/support/math/math_utils.h"
#include "fmt/std.h"

using namespace srsran;

// Maximum number of resource block groups per BWP.
static constexpr unsigned MAX_NOF_RBGS = 18;

// List of unique DCI sizes. Used to count the number of different DCI payload sizes generated by the size alignment
// procedure.
struct unique_dci_sizes_list {
  // Maximum number of different DCI payload sizes, given the current implementation.
  static constexpr unsigned MAX_NOF_DCI_SIZES = 4;

  // Statically allocated vector holding the unique DCI payload sizes.
  static_vector<unsigned, MAX_NOF_DCI_SIZES> unique_dci_sizes{};

  // Inserts a DCI size if it is not currently in the list.
  void insert_if_unique(unsigned dci_size)
  {
    srsran_assert((dci_size != 0), "The DCI size cannot be zero.");

    bool is_unique = (std::find(unique_dci_sizes.begin(), unique_dci_sizes.end(), dci_size)) == unique_dci_sizes.end();

    if (is_unique) {
      srsran_assert((count() < MAX_NOF_DCI_SIZES), "The maximum number of unique DCI sizes has been exceeded");

      unique_dci_sizes.emplace_back(dci_size);
    }
  }

  // Gets the number of unique DCI sizes in the list.
  unsigned count() const { return unique_dci_sizes.size(); }
};

// Checks that the Maximum CBG per TB value is valid, as per TS38.331 Section 6.3.2, Information Elements
// PUSCH-ServingCellConfig and PDSCH-ServingCellConfig
static bool max_cbg_tb_is_valid(unsigned max_cbg_tb)
{
  switch (max_cbg_tb) {
    case 2:
    case 4:
    case 6:
    case 8:
      return true;
    default:
      return false;
  }
}

// Computes the number of information bits before padding for a DCI format 0_0 message.
static dci_0_0_size dci_f0_0_bits_before_padding(unsigned N_rb_ul_bwp)
{
  dci_0_0_size sizes = {};

  // Identifier for DCI formats - 1 bit.
  ++sizes.total;

  // Frequency domain resource assignment. Number of bits as per TS38.214 Section 6.1.2.2.2.
  sizes.frequency_resource = units::bits(log2_ceil(N_rb_ul_bwp * (N_rb_ul_bwp + 1) / 2));
  sizes.total += sizes.frequency_resource;

  // Time domain resource assignment - 4 bit.
  sizes.total += units::bits(4);

  // Frequency hopping flag - 1 bit.
  ++sizes.total;

  // Modulation and coding scheme - 5 bit.
  sizes.total += units::bits(5);

  // New data indicator - 1 bit.
  ++sizes.total;

  // Redundancy version - 2 bit.
  sizes.total += units::bits(2);

  // HARQ process number - 4 bit.
  sizes.total += units::bits(4);

  // TPC command for scheduled PUSCH - 2 bit.
  sizes.total += units::bits(2);

  return sizes;
}

// Computes the number of information bits before padding for a DCI format 1_0 message.
static dci_1_0_size dci_f1_0_bits_before_padding(unsigned N_rb_dl_bwp)
{
  dci_1_0_size sizes = {};

  // Contribution to the DCI payload size that is fixed. It is the same number of bits for all format 1_0 variants.
  sizes.total = units::bits(28U);

  // Frequency domain resource assignment. Number of bits as per TS38.214 Section 5.1.2.2.2.
  sizes.frequency_resource = units::bits(log2_ceil(N_rb_dl_bwp * (N_rb_dl_bwp + 1) / 2));
  sizes.total += sizes.frequency_resource;

  return sizes;
}

// Computes the BWP indicator field size for DCI formats 0_1 and 1_1.
static unsigned bwp_indicator_size(unsigned nof_bwp_rrc)
{
  unsigned bwp_ind_size = 0;
  if (nof_bwp_rrc > 0) {
    unsigned n_bwp = nof_bwp_rrc;
    if (n_bwp <= 3) {
      ++n_bwp;
    }
    bwp_ind_size = log2_ceil(n_bwp);
    srsran_assert(bwp_ind_size <= 2,
                  "The derived BWP indicator field size, i.e., {} exceeds the maximum expected size of 2",
                  bwp_ind_size);
  }
  return bwp_ind_size;
}

// Determines the size of the frequency domain resource assignment field for DCI formats 0_1 and 1_1.
static unsigned freq_resource_assignment_size(resource_allocation     res_allocation_type,
                                              std::optional<unsigned> nof_rb_groups,
                                              unsigned                nof_prb_bwp)
{
  unsigned freq_resource_size = 0;
  switch (res_allocation_type) {
    case resource_allocation::resource_allocation_type_0:
      // For resource allocation type 0, the field size is the number of UL/DL RBG, as per TS38.214 Section 6.1.2.2.1 /
      // 5.1.2.2.1.
      freq_resource_size = nof_rb_groups.value();
      break;
    case resource_allocation::resource_allocation_type_1:
      // For resource allocation type 1, the field size is derived from the bandwidth of the active BWP, as per TS38.212
      // Section 7.3.1.
      freq_resource_size = log2_ceil(nof_prb_bwp * (nof_prb_bwp + 1) / 2);
      break;
    case resource_allocation::dynamic_switch:
      // For dynamic resource allocation type, the field size is determined by the resource allocation type that results
      // in a larger payload, and one extra bit is added to provide dynamic selection between type 0 and type 1.
      freq_resource_size = 1 + std::max(nof_rb_groups.value(), log2_ceil(nof_prb_bwp * (nof_prb_bwp + 1) / 2));
      break;
  }
  return freq_resource_size;
}

// Computes the UL precoding information field size for DCI format 0_1 from TS38.212 Table 7.3.1.1.2-2.
static units::bits ul_precoding_info_size_4port(tx_scheme_codebook_subset codebook_subset)
{
  using namespace units::literals;
  switch (codebook_subset) {
    case tx_scheme_codebook_subset::fully_and_partial_and_non_coherent:
      return 6_bits;
    case tx_scheme_codebook_subset::partial_and_non_coherent:
      return 5_bits;
    case tx_scheme_codebook_subset::non_coherent:
    default:
      return 4_bits;
  }
}

// Computes the UL precoding information field size for DCI format 0_1 from TS38.212 Table 7.3.1.1.2-3.
static units::bits ul_precoding_info_size_4port_maxrank1(tx_scheme_codebook_subset codebook_subset)
{
  using namespace units::literals;
  switch (codebook_subset) {
    case tx_scheme_codebook_subset::fully_and_partial_and_non_coherent:
      return 5_bits;
    case tx_scheme_codebook_subset::partial_and_non_coherent:
      return 4_bits;
    case tx_scheme_codebook_subset::non_coherent:
    default:
      return 2_bits;
  }
}

// Computes the UL precoding information field size for DCI format 0_1 from TS38.212 Table 7.3.1.1.2-4.
static units::bits ul_precoding_info_size_2port(tx_scheme_codebook_subset codebook_subset)
{
  using namespace units::literals;
  srsran_assert(codebook_subset != tx_scheme_codebook_subset::partial_and_non_coherent,
                "Codebook subset \"partial and non-coherent\" is not supported with two ports.",
                fmt::underlying(codebook_subset));

  if (codebook_subset == tx_scheme_codebook_subset::fully_and_partial_and_non_coherent) {
    return 4_bits;
  }

  return 2_bits;
}

// Computes the UL precoding information field size for DCI format 0_1 from TS38.212 Table 7.3.1.1.2-5.
static units::bits ul_precoding_info_size_2port_maxrank1(tx_scheme_codebook_subset codebook_subset)
{
  using namespace units::literals;
  srsran_assert(codebook_subset != tx_scheme_codebook_subset::partial_and_non_coherent,
                "Codebook subset \"partial and non-coherent\" is not supported with two ports.",
                fmt::underlying(codebook_subset));

  if (codebook_subset == tx_scheme_codebook_subset::fully_and_partial_and_non_coherent) {
    return 3_bits;
  }

  return 1_bits;
}

// Computes the UL precoding information field size for DCI format 0_1.
static units::bits ul_precoding_info_size(pusch_tx_scheme_configuration tx_scheme_config,
                                          unsigned                      nof_antenna_ports,
                                          bool                          transform_precoding_enabled)
{
  using namespace units::literals;

  // Non-codebook transmission scheme does not contain precoding information bits.
  if (std::holds_alternative<tx_scheme_non_codebook>(tx_scheme_config)) {
    return 0_bits;
  }

  // Codebook transmission scheme does not work with one antenna port.
  if (nof_antenna_ports == 1) {
    return 0_bits;
  }

  // Extract codebook transmission scheme parameters.
  srsran_assert(std::holds_alternative<tx_scheme_codebook>(tx_scheme_config), "Transmission scheme is not codebook.");
  const tx_scheme_codebook& codebook_config = std::get<tx_scheme_codebook>(tx_scheme_config);

  if ((nof_antenna_ports == 4) && (codebook_config.max_rank == 1)) {
    return ul_precoding_info_size_4port_maxrank1(codebook_config.codebook_subset);
  }

  if (nof_antenna_ports == 4) {
    return ul_precoding_info_size_4port(codebook_config.codebook_subset);
  }

  if ((nof_antenna_ports == 2) && (codebook_config.max_rank == 1)) {
    return ul_precoding_info_size_2port_maxrank1(codebook_config.codebook_subset);
  }

  return ul_precoding_info_size_2port(codebook_config.codebook_subset);
}

// Computes the UL antenna ports field size for a specific DM-RS configuration.
static unsigned ul_dmrs_ports_size(dmrs_config_type dmrs_type, dmrs_max_length dmrs_len, bool transform_precoding)
{
  // 2 bits as defined by Tables 7.3.1.1.2-6, if transform precoder is enabled, dmrs-Type=1, and maxLength=1.
  if (transform_precoding && (dmrs_type == dmrs_config_type::type1) && (dmrs_len == dmrs_max_length::len1)) {
    return 2;
  }

  // 4 bits as defined by Tables 7.3.1.1.2-7, if transform precoder is enabled, dmrs-Type=1, and maxLength=2.
  if (transform_precoding && (dmrs_type == dmrs_config_type::type1) && (dmrs_len == dmrs_max_length::len2)) {
    return 4;
  }

  // 3 bits as defined by Tables 7.3.1.1.2-8/9/10/11, if transform precoder is disabled, dmrs-Type=1, and maxLength=1.
  if (!transform_precoding && (dmrs_type == dmrs_config_type::type1) && (dmrs_len == dmrs_max_length::len1)) {
    return 3;
  }

  // 4 bits as defined by Tables 7.3.1.1.2-12/13/14/15, if transform precoder is disabled, dmrs-Type=1, and
  // maxLength=2.
  if (!transform_precoding && (dmrs_type == dmrs_config_type::type1) && (dmrs_len == dmrs_max_length::len2)) {
    return 4;
  }

  // 4 bits as defined by Tables 7.3.1.1.2-16/17/18/19, if transform precoder is disabled, dmrs-Type=2, and
  // maxLength=1.
  if (!transform_precoding && (dmrs_type == dmrs_config_type::type2) && (dmrs_len == dmrs_max_length::len1)) {
    return 4;
  }

  // 5 bits as defined by Tables 7.3.1.1.2-20/21/22/23, if transform precoder is disabled, dmrs-Type=2, and
  // maxLength=2.
  if (!transform_precoding && (dmrs_type == dmrs_config_type::type2) && (dmrs_len == dmrs_max_length::len2)) {
    return 5;
  }

  srsran_assertion_failure("Invalid combination of PUSCH DM-RS and transform precoding parameters.");
  return 0;
}

// Computes the DL antenna ports field size for a specific DM-RS configuration.
static unsigned dl_dmrs_ports_size(dmrs_config_type dmrs_type, dmrs_max_length dmrs_len)
{
  // 4, 5 or 6 bits as defined by Tables 7.3.1.2.2-1/2/3/4.
  unsigned ports_size = 4;
  if (dmrs_type == dmrs_config_type::type2) {
    ++ports_size;
  }
  if (dmrs_len == dmrs_max_length::len2) {
    ++ports_size;
  }
  return ports_size;
}

// Computes the antenna ports field size for DCI format 0_1.
static units::bits ul_ports_size(std::optional<dmrs_config_type> dmrs_A_type,
                                 std::optional<dmrs_max_length>  dmrs_A_max_len,
                                 std::optional<dmrs_config_type> dmrs_B_type,
                                 std::optional<dmrs_max_length>  dmrs_B_max_len,
                                 bool                            transform_precoding_enabled)
{
  bool mapping_type_A_configured = dmrs_A_type.has_value() && dmrs_A_max_len.has_value();
  bool mapping_type_B_configured = dmrs_B_type.has_value() && dmrs_B_max_len.has_value();

  units::bits pusch_dmrs_A_ports_size =
      mapping_type_A_configured
          ? units::bits(ul_dmrs_ports_size(dmrs_A_type.value(), dmrs_A_max_len.value(), transform_precoding_enabled))
          : units::bits(0);

  units::bits pusch_dmrs_B_ports_size =
      mapping_type_B_configured
          ? units::bits(ul_dmrs_ports_size(dmrs_B_type.value(), dmrs_B_max_len.value(), transform_precoding_enabled))
          : units::bits(0);

  return std::max(pusch_dmrs_A_ports_size, pusch_dmrs_B_ports_size);
}

// Computes the antenna ports field size for DCI format 1_1.
static units::bits dl_ports_size(std::optional<dmrs_config_type> dmrs_A_type,
                                 std::optional<dmrs_max_length>  dmrs_A_max_len,
                                 std::optional<dmrs_config_type> dmrs_B_type,
                                 std::optional<dmrs_max_length>  dmrs_B_max_len)
{
  bool mapping_type_A_configured = dmrs_A_type.has_value() && dmrs_A_max_len.has_value();
  bool mapping_type_B_configured = dmrs_B_type.has_value() && dmrs_B_max_len.has_value();

  units::bits pdsch_dmrs_A_ports_size =
      mapping_type_A_configured ? units::bits(dl_dmrs_ports_size(dmrs_A_type.value(), dmrs_A_max_len.value()))
                                : units::bits(0);

  units::bits pdsch_dmrs_B_ports_size =
      mapping_type_B_configured ? units::bits(dl_dmrs_ports_size(dmrs_B_type.value(), dmrs_B_max_len.value()))
                                : units::bits(0);

  return std::max(pdsch_dmrs_A_ports_size, pdsch_dmrs_B_ports_size);
}

// Computes the SRS resource indicator field size for DCI format 0_1.
static unsigned srs_resource_indicator_size(const dci_size_config& dci_config)
{
  srsran_assert(dci_config.pusch_tx_scheme.has_value(), "Transmit scheme is not present.");

  // SRS resource indicator size for non-codebook based transmission.
  if (std::holds_alternative<tx_scheme_non_codebook>(dci_config.pusch_tx_scheme.value())) {
    // Derived from TS38.212 Table 7.3.1.1.2-28.
    switch (dci_config.nof_srs_resources) {
      case 1:
        // If only a single SRS resource is configured, the SRS resource indicator does not occupy any bit.
        return 0;
      case 2:
        return 1;
      case 3:
      case 4:
      default:
        return 2;
    }
  }

  // SRS resource indicator size for codebook based transmission.
  return log2_ceil(dci_config.nof_srs_resources);
}

// Computes the number of information bits before padding for a DCI format 0_1 message.
static dci_0_1_size dci_f0_1_bits_before_padding(const dci_size_config& dci_config)
{
  dci_0_1_size sizes = {};

  // Identifier for DCI formats - 1 bit.
  ++sizes.total;

  // Carrier indicator - 0 or 3 bits.
  sizes.carrier_indicator = dci_config.cross_carrier_configured ? units::bits(3) : units::bits(0);
  sizes.total += sizes.carrier_indicator;

  // UL/SUL indicator - 0 or 1 bit.
  sizes.ul_sul_indicator = dci_config.sul_configured ? units::bits(1) : units::bits(0);
  sizes.total += sizes.ul_sul_indicator;

  // BWP indicator - 0, 1 or 2 bits.
  sizes.bwp_indicator = units::bits(bwp_indicator_size(dci_config.nof_ul_bwp_rrc));
  sizes.total += sizes.bwp_indicator;

  // Frequency domain resource assignment - number of bits as per TS38.212 Section 7.3.1.1.2.
  sizes.frequency_resource = units::bits(freq_resource_assignment_size(
      dci_config.pusch_res_allocation_type, dci_config.nof_ul_rb_groups, dci_config.ul_bwp_active_bw));
  sizes.total += sizes.frequency_resource;

  // Time domain resource assignment - 0, 1, 2, 3 or 4 bits.
  sizes.time_resource = units::bits(log2_ceil(dci_config.nof_ul_time_domain_res));
  sizes.total += sizes.time_resource;

  // Frequency hopping flag - 0 or 1 bit.
  sizes.freq_hopping_flag = (dci_config.pusch_res_allocation_type != resource_allocation::resource_allocation_type_0) &&
                                    dci_config.frequency_hopping_configured
                                ? units::bits(1)
                                : units::bits(0);
  sizes.total += sizes.freq_hopping_flag;

  // Modulation and coding scheme - 5 bits.
  sizes.total += units::bits(5);

  // New Data indicator - 1 bit.
  ++sizes.total;

  // Redundancy version - 2 bits.
  sizes.total += units::bits(2);

  // HARQ process number - 4 bits.
  sizes.total += units::bits(4);

  // First downlink assignment index - 1 or 2 bits.
  sizes.first_dl_assignment_idx =
      (dci_config.pdsch_harq_ack_cb == pdsch_harq_ack_codebook::dynamic) ? units::bits(2) : units::bits(1);
  sizes.total += sizes.first_dl_assignment_idx;

  // Second downlink assignment index - 0 or 2 bits.
  sizes.second_dl_assignment_idx =
      (dci_config.pdsch_harq_ack_cb == pdsch_harq_ack_codebook::dynamic) && dci_config.dynamic_dual_harq_ack_cb.value()
          ? units::bits(2)
          : units::bits(0);
  sizes.total += sizes.second_dl_assignment_idx;

  // TPC command for scheduled PUSCH - 2 bits.
  sizes.total += units::bits(2);

  // SRS resource indicator.
  sizes.srs_resource_indicator = units::bits(srs_resource_indicator_size(dci_config));
  sizes.total += sizes.srs_resource_indicator;

  // Precoding information and number of layers - 0, 1, 2, 3, 4, 5 or 6 bits.
  if (dci_config.nof_srs_ports.has_value() && dci_config.pusch_tx_scheme.has_value()) {
    sizes.precoding_info_nof_layers = ul_precoding_info_size(
        dci_config.pusch_tx_scheme.value(), dci_config.nof_srs_ports.value(), dci_config.transform_precoding_enabled);
    sizes.total += sizes.precoding_info_nof_layers;
  }

  // Antenna ports - 2, 3, 4 or 5 bits.
  sizes.antenna_ports = ul_ports_size(dci_config.pusch_dmrs_A_type,
                                      dci_config.pusch_dmrs_A_max_len,
                                      dci_config.pusch_dmrs_B_type,
                                      dci_config.pusch_dmrs_B_max_len,
                                      dci_config.transform_precoding_enabled);
  sizes.total += sizes.antenna_ports;

  // SRS request - 2 or 3 bits.
  sizes.srs_request = dci_config.sul_configured ? units::bits(3) : units::bits(2);
  sizes.total += sizes.srs_request;

  // CSI request - 0, 1, 2, 3, 4, 5 or 6 bits.
  sizes.csi_request = units::bits(dci_config.report_trigger_size);
  sizes.total += sizes.csi_request;

  // CBG Transmission Information (CBGTI) - 0, 2, 4, 6 or 8 bits.
  if (dci_config.max_cbg_tb_pusch.has_value()) {
    sizes.cbg_transmission_info = units::bits(dci_config.max_cbg_tb_pusch.value());
    sizes.total += sizes.cbg_transmission_info;
  }

  // PT-RS/DM-RS association - 0 or 2 bits.
  // ... Not implemented.

  // Beta offset indicator - 0 or 2 bits.
  sizes.beta_offset_indicator = dci_config.dynamic_beta_offsets ? units::bits(2) : units::bits(0);
  sizes.total += sizes.beta_offset_indicator;

  // DM-RS sequence initialization - 0 or 1 bit.
  sizes.dmrs_seq_initialization = dci_config.transform_precoding_enabled ? units::bits(0) : units::bits(1);
  sizes.total += sizes.dmrs_seq_initialization;

  // UL-SCH indicator - 1 bit.
  ++sizes.total;

  return sizes;
}

// Computes the number of information bits before padding for a DCI format 1_1 message.
static dci_1_1_size dci_f1_1_bits_before_padding(const dci_size_config& dci_config)
{
  dci_1_1_size sizes = {};

  // Identifier for DCI formats - 1 bit.
  ++sizes.total;

  // Carrier indicator - 0 or 3 bits.
  sizes.carrier_indicator = dci_config.cross_carrier_configured ? units::bits(3) : units::bits(0);
  sizes.total += sizes.carrier_indicator;

  // BWP indicator - 0, 1 or 2 bits.
  sizes.bwp_indicator = units::bits(bwp_indicator_size(dci_config.nof_dl_bwp_rrc));
  sizes.total += sizes.bwp_indicator;

  // Frequency domain resource assignment - number of bits as per TS38.212 Section 7.3.1.2.2.
  sizes.frequency_resource = units::bits(freq_resource_assignment_size(
      dci_config.pdsch_res_allocation_type, dci_config.nof_dl_rb_groups, dci_config.dl_bwp_active_bw));
  sizes.total += sizes.frequency_resource;

  // Time domain resource assignment - 0, 1, 2, 3 or 4 bits.
  sizes.time_resource = units::bits(log2_ceil(dci_config.nof_dl_time_domain_res));
  sizes.total += sizes.time_resource;

  // VRB-to-PRB mapping - 0 or 1 bit.
  sizes.vrb_prb_mapping = (dci_config.pdsch_res_allocation_type != resource_allocation::resource_allocation_type_0) &&
                                  dci_config.interleaved_vrb_prb_mapping.value()
                              ? units::bits(1)
                              : units::bits(0);
  sizes.total += sizes.vrb_prb_mapping;

  // PRB bundling size indicator - 0 or 1 bit.
  sizes.prb_bundling_size_indicator = dci_config.dynamic_prb_bundling ? units::bits(1) : units::bits(0);
  sizes.total += sizes.prb_bundling_size_indicator;

  // Rate matching indicator - 0, 1 or 2 bits.
  unsigned nof_rm_pattern_groups =
      static_cast<unsigned>(dci_config.rm_pattern_group1) + static_cast<unsigned>(dci_config.rm_pattern_group2);
  sizes.rate_matching_indicator = units::bits(nof_rm_pattern_groups);
  sizes.total += sizes.rate_matching_indicator;

  // ZP CSI-RS trigger - 0, 1 or 2 bits.
  sizes.zp_csi_rs_trigger = units::bits(log2_ceil(dci_config.nof_aperiodic_zp_csi + 1));
  sizes.total += sizes.zp_csi_rs_trigger;

  // Modulation and coding scheme for TB 1 - 5 bits.
  sizes.total += units::bits(5);

  // New data indicator for TB 1 - 1 bit.
  ++sizes.total;

  // Redundancy version for TB 1 - 2 bits.
  sizes.total += units::bits(2);

  if (dci_config.pdsch_two_codewords) {
    // Modulation and coding scheme for TB 2 - 0 or 5 bits.
    sizes.tb2_modulation_coding_scheme = units::bits(5);
    sizes.total += sizes.tb2_modulation_coding_scheme;

    // New data indicator for TB 2 - 0 or 1 bit.
    sizes.tb2_new_data_indicator = units::bits(1);
    sizes.total += sizes.tb2_new_data_indicator;

    // Redundancy version for TB 2 - 0 or 2 bits.
    sizes.tb2_redundancy_version = units::bits(2);
    sizes.total += sizes.tb2_redundancy_version;
  }

  // HARQ process number - 4 bits.
  sizes.total += units::bits(4);

  // Downlink Assignment Index (DAI) - 0, 2 or 4 bits.
  if (dci_config.pdsch_harq_ack_cb == pdsch_harq_ack_codebook::dynamic) {
    sizes.downlink_assignment_index = dci_config.multiple_scells ? units::bits(4) : units::bits(2);
    sizes.total += sizes.downlink_assignment_index;
  }

  // TPC command for scheduled PUCCH - 2 bits.
  sizes.total += units::bits(2);

  // PUCCH resource indicator - 3 bits as per TS38.213 Section 9.2.3.
  sizes.total += units::bits(3);

  // PDSCH to HARQ feedback timing indicator - 0, 1, 2 or 3 bits.
  sizes.pdsch_harq_fb_timing_indicator = units::bits(log2_ceil(dci_config.nof_pdsch_ack_timings));
  sizes.total += sizes.pdsch_harq_fb_timing_indicator;

  // Antenna ports - 4, 5 or 6 bits.
  sizes.antenna_ports = dl_ports_size(dci_config.pdsch_dmrs_A_type,
                                      dci_config.pdsch_dmrs_A_max_len,
                                      dci_config.pdsch_dmrs_B_type,
                                      dci_config.pdsch_dmrs_B_max_len);
  sizes.total += sizes.antenna_ports;

  // Transmission configuration indication - 0 or 3 bits.
  sizes.tx_config_indication = dci_config.pdsch_tci ? units::bits(3) : units::bits(0);
  sizes.total += sizes.tx_config_indication;

  // SRS request - 2 or 3 bits.
  sizes.srs_request = dci_config.sul_configured ? units::bits(3) : units::bits(2);
  sizes.total += sizes.srs_request;

  // CBG Transmission Information (CBGTI) - 0, 2, 4, 6 or 8 bits.
  if (dci_config.max_cbg_tb_pdsch.has_value()) {
    sizes.cbg_transmission_info = units::bits(dci_config.max_cbg_tb_pdsch.value());
    sizes.total += sizes.cbg_transmission_info;
  }

  // CBG Flushing Out Information (CBGFI) - 0 or 1 bit.
  sizes.cbg_flushing_info = dci_config.cbg_flush_indicator ? units::bits(1) : units::bits(0);
  sizes.total += sizes.cbg_flushing_info;

  // DM-RS sequence initialization - 1 bit.
  ++sizes.total;

  return sizes;
}

static void assert_dci_size_config(const dci_size_config& dci_sz_cfg)
{
  [[maybe_unused]] std::string error_msg;
  [[maybe_unused]] auto        validate_dci_sz_cfg = [&dci_sz_cfg, &error_msg]() {
    error_type<std::string> dci_size_valid = validate_dci_size_config(dci_sz_cfg);
    bool                    is_success     = dci_size_valid.has_value();
    if (!is_success) {
      error_msg = dci_size_valid.error();
    }
    return is_success;
  };
  srsran_assert(validate_dci_sz_cfg(), "Invalid DCI size configuration: {}", error_msg);
}

dci_sizes srsran::get_dci_sizes(const dci_size_config& config)
{
  // Assert DCI size configuration parameters.
  assert_dci_size_config(config);

  dci_sizes final_sizes = {};

  // Step 0.
  // - Determine DCI format 0_0 monitored in a common search space according to TS38.212 Section 7.3.1.1.1 where
  // N_UL_BWP_RB is given by the size of the initial UL bandwidth part.
  const dci_0_0_size format0_0_info_bits_common = dci_f0_0_bits_before_padding(config.ul_bwp_initial_bw);

  // - Determine DCI format 1_0 monitored in a common search space according to TS38.212 Section 7.3.1.2.1 where
  // N_DL_BWP_RB given by:
  //   - the size of CORESET 0 if CORESET 0 is configured for the cell
  //   - the size of initial DL bandwidth part if CORESET 0 is not configured for the cell.
  const dci_1_0_size format1_0_info_bits_common =
      dci_f1_0_bits_before_padding((config.coreset0_bw != 0) ? config.coreset0_bw : config.dl_bwp_initial_bw);

  final_sizes.format0_0_common_size = format0_0_info_bits_common;
  final_sizes.format1_0_common_size = format1_0_info_bits_common;

  // - If DCI format 0_0 is monitored in common search space and if the number of information bits in the DCI format 0_0
  // prior to padding is less than the payload size of the DCI format 1_0 monitored in common search space for
  // scheduling the same serving cell, a number of zero padding bits are generated for the DCI format 0_0 until the
  // payload size equals that of the DCI format 1_0.
  if (format0_0_info_bits_common.total < format1_0_info_bits_common.total) {
    // The number of padding bits is computed here, including the single bit UL/SUL field. This field is located after
    // the padding, and it must only be included if the format 1_0 payload has a larger amount of bits before the
    // padding bits than the format 0_0 payload. Therefore, the UL/SUL can be though of as a field that takes the space
    // of the last padding bit within the format 0_0 payload, if present. See TS38.212 Sections 7.3.1.0 and 7.3.1.1.1.
    final_sizes.format0_0_common_size.padding_incl_ul_sul =
        format1_0_info_bits_common.total - format0_0_info_bits_common.total;

    // Update the DCI format 0_0 total payload size.
    final_sizes.format0_0_common_size.total += final_sizes.format0_0_common_size.padding_incl_ul_sul;
  }

  // - If DCI format 0_0 is monitored in common search space and if the number of information bits in the DCI format 0_0
  // prior to truncation is larger than the payload size of the DCI format 1_0 monitored in common search space for
  // scheduling the same serving cell, the bitwidth of the frequency domain resource assignment field in the DCI format
  // 0_0 is reduced by truncating the first few most significant bits such that the size of DCI format 0_0 equals the
  // size of the DCI format 1_0.
  else if (format0_0_info_bits_common.total > format1_0_info_bits_common.total) {
    units::bits nof_truncated_bits = format0_0_info_bits_common.total - format1_0_info_bits_common.total;
    final_sizes.format0_0_common_size.frequency_resource -= nof_truncated_bits;
    final_sizes.format0_0_common_size.total -= nof_truncated_bits;
  }

  srsran_assert(final_sizes.format1_0_common_size.total == final_sizes.format0_0_common_size.total,
                "DCI format 0_0 and 1_0 payload sizes must match");

  // Step 1.
  if (config.dci_0_0_and_1_0_ue_ss) {
    // - Determine DCI format 0_0 monitored in a UE-specific search space according to TS38.212 Section 7.3.1.1.1 where
    // N_UL_BWP_RB is the size of the active UL bandwidth part.
    const dci_0_0_size format0_0_info_bits_ue = dci_f0_0_bits_before_padding(config.ul_bwp_active_bw);

    // - Determine DCI format 1_0 monitored in a UE-specific search space according to TS38.212 Section 7.3.1.2.1 where
    // N_DL_BWP_RB is the size of the active DL bandwidth part.
    const dci_1_0_size format1_0_info_bits_ue = dci_f1_0_bits_before_padding(config.dl_bwp_active_bw);

    // - For a UE configured with supplementaryUplink in ServingCellConfig in a cell, if PUSCH is configured to be
    // transmitted on both the SUL and the non-SUL of the cell and if the number of information bits in DCI format 0_0
    // in UE-specific search space for the SUL is not equal to the number of information bits in DCI format 0_0 in
    // UE-specific search space for the non-SUL, a number of zero padding bits are generated for the smaller DCI format
    // 0_0 until the payload size equals that of the larger DCI format 0_0.

    // Not implemented.

    final_sizes.format0_0_ue_size = format0_0_info_bits_ue;
    final_sizes.format1_0_ue_size = format1_0_info_bits_ue;

    // - If DCI format 0_0 is monitored in UE-specific search space and if the number of information bits in the DCI
    // format 0_0 prior to padding is less than the payload size of the DCI format 1_0 monitored in UE-specific search
    // space for scheduling the same serving cell, a number of zero padding bits are generated for the DCI format 0_0
    // until the payload size equals that of the DCI format 1_0.
    if (format0_0_info_bits_ue.total < format1_0_info_bits_ue.total) {
      units::bits nof_padding_bits_incl_ul_sul = format1_0_info_bits_ue.total - format0_0_info_bits_ue.total;
      final_sizes.format0_0_ue_size.value().padding_incl_ul_sul = nof_padding_bits_incl_ul_sul;
      final_sizes.format0_0_ue_size.value().total += nof_padding_bits_incl_ul_sul;
    }

    // - If DCI format 1_0 is monitored in UE-specific search space and if the number of information bits in the DCI
    // format 1_0 prior to padding is less than the payload size of the DCI format 0_0 monitored in UE-specific search
    // space for scheduling the same serving cell, zeros shall be appended to the DCI format 1_0 until the payload size
    // equals that of the DCI format 0_0
    else if (format1_0_info_bits_ue.total < format0_0_info_bits_ue.total) {
      units::bits nof_padding_bits                  = format0_0_info_bits_ue.total - format1_0_info_bits_ue.total;
      final_sizes.format1_0_ue_size.value().padding = nof_padding_bits;
      final_sizes.format1_0_ue_size.value().total += final_sizes.format1_0_ue_size.value().padding;
    }

    srsran_assert(final_sizes.format1_0_ue_size.value().total == final_sizes.format0_0_ue_size.value().total,
                  "DCI format 0_0 and 1_0 payload sizes must match");
  }

  // Step 2.
  if (config.dci_0_1_and_1_1_ue_ss) {
    // Determine the size of DCI format 0_1 according to TS38.212 Section 7.3.1.1.2.
    dci_0_1_size format0_1_ue_size = dci_f0_1_bits_before_padding(config);

    // Determine the size of DCI format 1_1 according to TS38.212 Section 7.3.1.2.2.
    dci_1_1_size format1_1_ue_size = dci_f1_1_bits_before_padding(config);

    // - For a UE configured with supplementaryUplink in ServingCellConfig in a cell, if PUSCH is configured to be
    // transmitted on both the SUL and the non-SUL of the cell and if the number of information bits in format 0_1 for
    // the SUL is not equal to the number of information bits in format 0_1 for the non-SUL, zeros shall be appended to
    // smaller format 0_1 until the payload size equals that of the larger format 0_1.

    // Not implemented.

    // - If the size of DCI format 0_1 monitored in a UE-specific search space equals that of a DCI format 0_0/1_0
    // monitored in another UE-specific search space, one bit of zero padding shall be appended to DCI format 0_1.
    if (config.dci_0_0_and_1_0_ue_ss && (format0_1_ue_size.total == final_sizes.format0_0_ue_size.value().total)) {
      format0_1_ue_size.padding = units::bits(1);
      format0_1_ue_size.total += format0_1_ue_size.padding;
    }

    // - If the size of DCI format 1_1 monitored in a UE-specific search space equals that of a DCI format 0_0/1_0
    // monitored in another UE-specific search space, one bit of zero padding shall be appended to DCI format 1_1.
    if (config.dci_0_0_and_1_0_ue_ss && (format1_1_ue_size.total == final_sizes.format1_0_ue_size.value().total)) {
      format1_1_ue_size.padding = units::bits(1);
      format1_1_ue_size.total += format1_1_ue_size.padding;
    }

    final_sizes.format0_1_ue_size = format0_1_ue_size;
    final_sizes.format1_1_ue_size = format1_1_ue_size;
  }

  // Step 3.
  // If both of the following conditions are fulfilled the size alignment procedure is complete.
  // - The total number of different DCI sizes configured to monitor is no more than 4 for the cell.
  // - The total number of different DCI sizes with C-RNTI configured to monitor is no more than 3 for the cell.

  // Empty list of unique DCI sizes.
  unique_dci_sizes_list unique_dci_sizes;

  // Fallback DCI formats monitored in Common Search Space are always included.
  unique_dci_sizes.insert_if_unique(final_sizes.format0_0_common_size.total.value());

  // Fallback DCI formats monitored in UE-specific Search Space. Count them if they are included in the DCI size
  // alignment procedure and the resulting payload size is different from the fallback DCI formats monitored in a CSS.
  if (config.dci_0_0_and_1_0_ue_ss) {
    unique_dci_sizes.insert_if_unique(final_sizes.format0_0_ue_size.value().total.value());
  }

  // Non-fallback DCI formats monitored in UE-specific Search Space. Count them if they are included in the DCI size
  // alignment procedure and the resulting payload size is different from other DCI formats.
  if (config.dci_0_1_and_1_1_ue_ss) {
    unique_dci_sizes.insert_if_unique(final_sizes.format0_1_ue_size.value().total.value());
    unique_dci_sizes.insert_if_unique(final_sizes.format1_1_ue_size.value().total.value());
  }

  // Get the actual number of distinct DCI payload sizes. No special DCI formats are implemented, so the most
  // restrictive condition imposed by the size alignment procedure is no more than 3 DCI sizes scrambled by C-RNTI.
  unsigned nof_c_rnti_dci_sizes = unique_dci_sizes.count();

  // Step 4.
  // If the above conditions are not met, set the size of the fallback DCI formats in a UE-specific Search Space to the
  // size of the fallback DCI formats monitored in a Common Search Space.
  if (nof_c_rnti_dci_sizes > 3) {
    final_sizes.format0_0_ue_size = final_sizes.format0_0_common_size;
    final_sizes.format1_0_ue_size = final_sizes.format1_0_common_size;
  }

  return final_sizes;
}

dci_payload srsran::dci_0_0_c_rnti_pack(const dci_0_0_c_rnti_configuration& config)
{
  srsran_assert(config.payload_size.total.value() >= 12,
                "DCI total payload size is {}, it must be at least 12 bit long for DCI Format 0_0 and C-RNTI.",
                config.payload_size.total);

  dci_payload payload;
  units::bits frequency_resource_nof_bits = config.payload_size.frequency_resource;

  // Identifier for DCI formats - 1 bit. This field is always 0, indicating an UL DCI format.
  payload.push_back(0x00U, 1);

  if (config.frequency_hopping_flag) {
    // Assert that the number of bits used to pack the frequency hopping offset is valid.
    srsran_assert((config.N_ul_hop == 1) || (config.N_ul_hop == 2),
                  "DCI frequency offset number of bits must be either 1 or 2");

    // Assert that the frequency resource field has enough bits to include the frequency hopping offset.
    srsran_assert(config.N_ul_hop < frequency_resource_nof_bits.value(),
                  "The frequency resource field must have enough bits to hold the frequency hopping offset");

    // Assert that the frequency hopping offset can be packed with the allocated bits.
    srsran_assert(config.hopping_offset < (1U << config.N_ul_hop),
                  "DCI frequency offset value ({}) cannot be packed with the allocated number of bits ({})",
                  config.hopping_offset,
                  config.N_ul_hop);

    // Truncate the frequency resource allocation field.
    frequency_resource_nof_bits -= units::bits(config.N_ul_hop);

    // Frequency hopping offset - N_ul_hop bits.
    payload.push_back(config.hopping_offset, config.N_ul_hop);
  }

  // Frequency domain resource assignment - frequency_resource_nof_bits bits.
  payload.push_back(config.frequency_resource, frequency_resource_nof_bits.value());

  // Time domain resource assignment - 4 bit.
  payload.push_back(config.time_resource, 4);

  // Frequency hopping flag - 1 bit.
  payload.push_back(config.frequency_hopping_flag, 1);

  // Modulation coding scheme - 5 bits.
  payload.push_back(config.modulation_coding_scheme, 5);

  // New data indicator - 1 bit.
  payload.push_back(config.new_data_indicator, 1);

  // Redundancy version - 2 bit.
  payload.push_back(config.redundancy_version, 2);

  // HARQ process number - 4 bit.
  payload.push_back(config.harq_process_number, 4);

  // TPC command for scheduled PUSCH - 2 bit.
  payload.push_back(config.tpc_command, 2);

  if (config.payload_size.padding_incl_ul_sul.value() > 0) {
    if (config.ul_sul_indicator.has_value()) {
      // UL/SUL field is included if it is present in the DCI message and the number of DCI format 1_0 bits before
      // padding is larger than the number of DCI format 0_0 bits before padding.
      constexpr unsigned nof_ul_sul_bit = 1U;
      // Padding bits, if necessary, as per TS38.212 Section 7.3.1.0.
      payload.push_back(0x00U, config.payload_size.padding_incl_ul_sul.value() - nof_ul_sul_bit);

      // UL/SUL indicator - 1 bit.
      payload.push_back(config.ul_sul_indicator.value(), nof_ul_sul_bit);
    } else {
      // UL/SUL field is not included otherwise.
      payload.push_back(0x00U, config.payload_size.padding_incl_ul_sul.value());
    }
  }

  // Assert total payload size.
  srsran_assert(units::bits(payload.size()) == config.payload_size.total,
                "Constructed payload size (i.e., {}) does not match expected payload size. Expected sizes:\n{}",
                units::bits(payload.size()),
                config.payload_size);

  return payload;
}

dci_payload srsran::dci_0_0_tc_rnti_pack(const dci_0_0_tc_rnti_configuration& config)
{
  srsran_assert(config.payload_size.total.value() >= 12,
                "DCI total payload size is {}, it must be at least 12 bit long for DCI Format 0_0 and TC-RNTI.",
                config.payload_size.total);

  units::bits frequency_resource_nof_bits = config.payload_size.frequency_resource;
  dci_payload payload;

  // Identifier for DCI formats - 1 bit. This field is always 0, indicating an UL DCI format.
  payload.push_back(0x00U, 1);

  unsigned freq_resource_payload = config.frequency_resource;

  if (config.frequency_hopping_flag) {
    // Assert that the number of bits used to pack the frequency hopping offset is valid.
    srsran_assert((config.N_ul_hop == 1) || (config.N_ul_hop == 2),
                  "DCI frequency offset number of bits must be either 1 or 2");

    // Assert that the frequency resource field has enough bits to include the frequency hopping offset.
    srsran_assert(config.N_ul_hop < frequency_resource_nof_bits.value(),
                  "The frequency resource field must have enough bits to hold the frequency hopping offset");

    // Assert that the frequency hopping offset can be packed with the allocated bits.
    srsran_assert(config.hopping_offset < (1U << config.N_ul_hop),
                  "DCI frequency offset value ({}) cannot be packed with the allocated number of bits ({})",
                  config.hopping_offset,
                  config.N_ul_hop);

    // Position of the LSB bit of the hopping offset within the frequency domain resource assignment field, as per
    // TS38.212 Section 7.3.1.1.1.
    unsigned hopping_offset_lsb_pos = frequency_resource_nof_bits.value() - config.N_ul_hop;

    // Frequency resource mask, to truncate the frequency resource payload before adding the hopping offset bits.
    unsigned freq_resource_mask = (1U << hopping_offset_lsb_pos) - 1;

    // Add the frequency hopping offset to the frequency domain resource assignment field.
    freq_resource_payload =
        (config.frequency_resource & freq_resource_mask) + (config.hopping_offset << hopping_offset_lsb_pos);
  }

  // Frequency domain resource assignment - frequency_resource_nof_bits bits.
  payload.push_back(freq_resource_payload, frequency_resource_nof_bits.value());

  // Time domain resource assignment - 4 bit.
  payload.push_back(config.time_resource, 4);

  // Frequency hopping flag - 1 bit.
  payload.push_back(config.frequency_hopping_flag, 1);

  // Modulation coding scheme - 5 bits.
  payload.push_back(config.modulation_coding_scheme, 5);

  // New data indicator - 1 bit, reserved.
  payload.push_back(0x00U, 1);

  // Redundancy version - 2 bit.
  payload.push_back(config.redundancy_version, 2);

  // HARQ process number - 4 bit, reserved.
  payload.push_back(0x00U, 4);

  // TPC command for scheduled PUSCH - 2 bit.
  payload.push_back(config.tpc_command, 2);

  if (config.payload_size.padding_incl_ul_sul.value() > 0) {
    // Padding bits, including UL/SUL reserved field.
    payload.push_back(0x00U, config.payload_size.padding_incl_ul_sul.value());
  }

  // Assert total payload size.
  srsran_assert(units::bits(payload.size()) == config.payload_size.total,
                "Constructed payload size (i.e., {}) does not match expected payload size. Expected sizes:\n{}",
                units::bits(payload.size()),
                config.payload_size);

  return payload;
}

dci_payload srsran::dci_1_0_c_rnti_pack(const dci_1_0_c_rnti_configuration& config)
{
  srsran_assert(config.payload_size.total.value() >= 12,
                "DCI total payload size is {}, it must be at least 12 bit long for DCI Format 1_0 and C-RNTI.",
                config.payload_size.total);

  dci_payload payload;

  // Identifier for DCI formats - 1 bit. This field is always 1, indicating a DL DCI format.
  payload.push_back(0x01U, 1);

  // Frequency domain resource assignment - frequency_resource_nof_bits bits.
  payload.push_back(config.frequency_resource, config.payload_size.frequency_resource.value());

  // Time domain resource assignment - 4 bit.
  payload.push_back(config.time_resource, 4);

  // VRB-to-PRB mapping - 1 bit.
  payload.push_back(config.vrb_to_prb_mapping, 1);

  // Modulation coding scheme - 5 bits.
  payload.push_back(config.modulation_coding_scheme, 5);

  // New data indicator - 1 bit.
  payload.push_back(config.new_data_indicator, 1);

  // Redundancy version - 2 bit.
  payload.push_back(config.redundancy_version, 2);

  // HARQ process number - 4 bit.
  payload.push_back(config.harq_process_number, 4);

  // Downlink assignment index - 2 bit.
  payload.push_back(config.dl_assignment_index, 2);

  // TPC command for scheduled PUCCH - 2 bit.
  payload.push_back(config.tpc_command, 2);

  // PUCCH resource indicator - 3 bit.
  payload.push_back(config.pucch_resource_indicator, 3);

  // PDSCH to HARQ feedback timing indicator - 3 bit.
  payload.push_back(config.pdsch_harq_fb_timing_indicator, 3);

  // Padding - nof_padding_bits bits.
  payload.push_back(0x00U, config.payload_size.padding.value());

  // Assert total payload size.
  srsran_assert(units::bits(payload.size()) == config.payload_size.total,
                "Constructed payload size (i.e., {}) does not match expected payload size. Expected sizes:\n{}",
                units::bits(payload.size()),
                config.payload_size);

  return payload;
}

dci_payload srsran::dci_1_0_p_rnti_pack(const dci_1_0_p_rnti_configuration& config)
{
  units::bits frequency_resource_nof_bits(log2_ceil(config.N_rb_dl_bwp * (config.N_rb_dl_bwp + 1) / 2));
  dci_payload payload;

  // Short Message Indicator - 2 bits.
  switch (config.short_messages_indicator) {
    case dci_1_0_p_rnti_configuration::payload_info::scheduling_information:
      payload.push_back(0b01U, 2);
      break;
    case dci_1_0_p_rnti_configuration::payload_info::short_messages:
      payload.push_back(0b10U, 2);
      break;
    case dci_1_0_p_rnti_configuration::payload_info::both:
      payload.push_back(0b11U, 2);
      break;
  }

  // Short Messages - 8 bits.
  if (config.short_messages_indicator == dci_1_0_p_rnti_configuration::payload_info::scheduling_information) {
    // If only the scheduling information for paging is carried, this bit field is reserved.
    payload.push_back(0x00U, 8);
  } else {
    payload.push_back(config.short_messages, 8);
  }

  if (config.short_messages_indicator == dci_1_0_p_rnti_configuration::payload_info::short_messages) {
    // If only the short message is carried, the scheduling information for paging bit fields are reserved.
    payload.push_back(0x00U, frequency_resource_nof_bits.value() + 12);
  } else {
    // Frequency domain resource assignment - frequency_resource_nof_bits bits.
    payload.push_back(config.frequency_resource, frequency_resource_nof_bits.value());

    // Time domain resource assignment - 4 bits.
    payload.push_back(config.time_resource, 4);

    // VRB-to-PRB mapping - 1 bit.
    payload.push_back(config.vrb_to_prb_mapping, 1);

    // Modulation and coding scheme - 5 bits.
    payload.push_back(config.modulation_coding_scheme, 5);

    // Transport Block scaling - 2 bits.
    payload.push_back(config.tb_scaling, 2);
  }

  // Reserved bits: 6 bits.
  payload.push_back(0x00U, 6);

  return payload;
}

dci_payload srsran::dci_1_0_si_rnti_pack(const dci_1_0_si_rnti_configuration& config)
{
  units::bits frequency_resource_nof_bits(log2_ceil(config.N_rb_dl_bwp * (config.N_rb_dl_bwp + 1) / 2));
  dci_payload payload;

  // Frequency domain resource assignment - frequency_resource_nof_bits bits.
  payload.push_back(config.frequency_resource, frequency_resource_nof_bits.value());

  // Time domain resource assignment - 4 bit.
  payload.push_back(config.time_resource, 4);

  // VRB-to-PRB mapping - 1 bit.
  payload.push_back(config.vrb_to_prb_mapping, 1);

  // Modulation coding scheme - 5 bits.
  payload.push_back(config.modulation_coding_scheme, 5);

  // Redundancy version - 2 bits.
  payload.push_back(config.redundancy_version, 2);

  // System information indicator - 1 bit.
  payload.push_back(config.system_information_indicator, 1);

  // Reserved bits - 15 bit.
  payload.push_back(0x00U, 15);

  return payload;
}

dci_payload srsran::dci_1_0_ra_rnti_pack(const dci_1_0_ra_rnti_configuration& config)
{
  units::bits frequency_resource_nof_bits(log2_ceil(config.N_rb_dl_bwp * (config.N_rb_dl_bwp + 1) / 2));
  dci_payload payload;

  // Frequency domain resource assignment - frequency_resource_nof_bits bits.
  payload.push_back(config.frequency_resource, frequency_resource_nof_bits.value());

  // Time domain resource assignment - 4 bits.
  payload.push_back(config.time_resource, 4);

  // VRB-to-PRB mapping - 1 bit.
  payload.push_back(config.vrb_to_prb_mapping, 1);

  // Modulation and coding scheme - 5 bits.
  payload.push_back(config.modulation_coding_scheme, 5);

  // Transport Block scaling - 2 bits.
  payload.push_back(config.tb_scaling, 2);

  // Reserved bits - 16 bits.
  payload.push_back(0x00U, 16);

  return payload;
}

dci_payload srsran::dci_1_0_tc_rnti_pack(const dci_1_0_tc_rnti_configuration& config)
{
  units::bits frequency_resource_nof_bits(log2_ceil(config.N_rb_dl_bwp * (config.N_rb_dl_bwp + 1) / 2));
  dci_payload payload;

  // Identifier for DCI formats - 1 bit. This field is always 1, indicating a DL DCI format.
  payload.push_back(0x01U, 1);

  // Frequency domain resource assignment - frequency_resource_nof_bits bits.
  payload.push_back(config.frequency_resource, frequency_resource_nof_bits.value());

  // Time domain resource assignment - 4 bit.
  payload.push_back(config.time_resource, 4);

  // VRB-to-PRB mapping - 1 bit.
  payload.push_back(config.vrb_to_prb_mapping, 1);

  // Modulation coding scheme - 5 bits.
  payload.push_back(config.modulation_coding_scheme, 5);

  // New data indicator - 1 bit.
  payload.push_back(config.new_data_indicator, 1);

  // Redundancy version - 2 bit.
  payload.push_back(config.redundancy_version, 2);

  // HARQ process number - 4 bit.
  payload.push_back(config.harq_process_number, 4);

  // Downlink assignment index - 2 bit, reserved.
  payload.push_back(0x00U, 2);

  // TPC command for scheduled PUCCH - 2 bit.
  payload.push_back(config.tpc_command, 2);

  // PUCCH resource indicator - 3 bit.
  payload.push_back(config.pucch_resource_indicator, 3);

  // PDSCH to HARQ feedback timing indicator - 3 bit.
  payload.push_back(config.pdsch_harq_fb_timing_indicator, 3);

  return payload;
}

dci_payload srsran::dci_0_1_pack(const dci_0_1_configuration& config)
{
  srsran_assert(config.payload_size.total.value() >= 12,
                "DCI total payload size is {}, it must be at least 12 bit long for DCI Format 0_1.",
                config.payload_size.total);

  // Assertions for unsupported fields.
  srsran_assert(!config.ul_sul_indicator.has_value(), "UL/SUL indicator field is not currently supported.");
  srsran_assert(!config.ptrs_dmrs_association.has_value(), "PT-RS/DM-RS association field is not currently supported.");

  dci_payload payload;

  // Identifier for DCI formats - 1 bit. This field is always 0, indicating an UL DCI format.
  payload.push_back(0x00U, 1);

  // Carrier indicator - 0 or 3 bits.
  if (config.carrier_indicator.has_value()) {
    payload.push_back(config.carrier_indicator.value(), config.payload_size.carrier_indicator.value());
  }

  // UL/SUL indicator - 0 or 1 bit.
  if (config.ul_sul_indicator.has_value()) {
    payload.push_back(config.ul_sul_indicator.value(), config.payload_size.ul_sul_indicator.value());
  }

  // Bandwidth part indicator - 0, 1 or 2 bits.
  if (config.bwp_indicator.has_value()) {
    payload.push_back(config.bwp_indicator.value(), config.payload_size.bwp_indicator.value());
  }

  units::bits frequency_resource_nof_bits = config.payload_size.frequency_resource;

  if (config.dynamic_pusch_res_allocation_type.has_value()) {
    // Indicates the DCI resource allocation type if both resource allocation type 0 and type 1 are configured.
    unsigned dynamic_alloc_type_indicator =
        config.dynamic_pusch_res_allocation_type == dynamic_resource_allocation::type_0 ? 0 : 1;

    // The MSB bit of the frequency domain allocation field is used to indicate the resource allocation type, as per
    // TS38.212 Section 7.3.1.1.2.
    payload.push_back(dynamic_alloc_type_indicator, 1);

    // The rest of the LSB bits are used to pack the frequency domain resource allocation.
    frequency_resource_nof_bits -= units::bits(1);
  }

  if (config.frequency_hopping_flag.has_value() && (config.frequency_hopping_flag.value() == 1)) {
    // Assert that the number of bits used to pack the frequency hopping offset is valid.
    srsran_assert((config.N_ul_hop.value() == 1) || (config.N_ul_hop.value() == 2),
                  "DCI frequency offset number of bits must be either 1 or 2");

    // Assert that the frequency resource field has enough bits to include the frequency hopping offset.
    srsran_assert(config.N_ul_hop.value() < frequency_resource_nof_bits.value(),
                  "The frequency resource field must have enough bits to hold the frequency hopping offset");

    // Assert that the frequency hopping offset can be packed with the allocated bits.
    srsran_assert(config.hopping_offset.value() < (1U << config.N_ul_hop.value()),
                  "DCI frequency offset value, i.e., {} cannot be packed with the allocated number of bits, i.e., {}",
                  config.hopping_offset,
                  config.N_ul_hop);

    // Truncate the frequency resource allocation field.
    frequency_resource_nof_bits -= units::bits(config.N_ul_hop.value());

    // Frequency hopping offset - 1 or 2 bits.
    payload.push_back(config.hopping_offset.value(), config.N_ul_hop.value());
  }

  // Frequency domain resource assignment - frequency_resource_nof_bits bits.
  payload.push_back(config.frequency_resource, frequency_resource_nof_bits.value());

  // Time domain resource assignment - 0, 1, 2, 3 or 4 bits.
  if (config.payload_size.time_resource != units::bits(0)) {
    payload.push_back(config.time_resource, config.payload_size.time_resource.value());
  }

  // Frequency hopping flag - 0 or 1 bit.
  if (config.frequency_hopping_flag.has_value()) {
    payload.push_back(config.frequency_hopping_flag.value(), config.payload_size.freq_hopping_flag.value());
  }

  // Modulation coding scheme - 5 bits.
  payload.push_back(config.modulation_coding_scheme, 5);

  // New data indicator - 1 bit.
  payload.push_back(config.new_data_indicator, 1);

  // Redundancy version - 2 bits.
  payload.push_back(config.redundancy_version, 2);

  // HARQ process number - 4 bits.
  payload.push_back(config.harq_process_number, 4);

  // 1st downlink assignment index - 1 or 2 bits.
  payload.push_back(config.first_dl_assignment_index, config.payload_size.first_dl_assignment_idx.value());

  // 2nd downlink assignment index - 0 or 2 bits.
  if (config.second_dl_assignment_index.has_value()) {
    payload.push_back(config.second_dl_assignment_index.value(), config.payload_size.second_dl_assignment_idx.value());
  }

  // TPC command for scheduled PUSCH - 2 bits.
  payload.push_back(config.tpc_command, 2);

  // SRS resource indicator (SRI).
  payload.push_back(config.srs_resource_indicator, config.payload_size.srs_resource_indicator.value());

  // Precoding information and number of layers - 0 to 6 bits.
  payload.push_back(config.precoding_info_nof_layers, config.payload_size.precoding_info_nof_layers.value());

  // Antenna ports for PUSCH transmission - 2, 3, 4 or 5 bits.
  payload.push_back(config.antenna_ports, config.payload_size.antenna_ports.value());

  // SRS request - 2 or 3 bits.
  payload.push_back(config.srs_request, config.payload_size.srs_request.value());

  // CSI request - 0 to 6 bits.
  if (config.csi_request.has_value()) {
    payload.push_back(config.csi_request.value(), config.payload_size.csi_request.value());
  }

  // CBG Transmission Information (CBGTI) - 0, 2, 4, 6 or 8 bits.
  if (config.cbg_transmission_info.has_value()) {
    payload.push_back(config.cbg_transmission_info.value(), config.payload_size.cbg_transmission_info.value());
  }

  // PT-RS/DM-RS association - 0 or 2 bits.
  if (config.ptrs_dmrs_association.has_value()) {
    payload.push_back(config.ptrs_dmrs_association.value(), config.payload_size.ptrs_dmrs_association.value());
  }

  // Beta offset indicator - 0 or 2 bits.
  if (config.beta_offset_indicator.has_value()) {
    payload.push_back(config.beta_offset_indicator.value(), config.payload_size.beta_offset_indicator.value());
  }

  // DM-RS sequence initialization - 0 or 1 bit.
  if (config.dmrs_seq_initialization.has_value()) {
    payload.push_back(config.dmrs_seq_initialization.value(), config.payload_size.dmrs_seq_initialization.value());
  }

  // UL-SCH indicator - 1 bit.
  payload.push_back(config.ul_sch_indicator, 1);

  // Padding bits, if necessary, as per TS38.212 Section 7.3.1.0.
  if (config.payload_size.padding.value() > 0) {
    payload.push_back(0x00U, config.payload_size.padding.value());
  }

  // Assert total payload size.
  srsran_assert(units::bits(payload.size()) == config.payload_size.total,
                "Constructed payload size {} does not match expected payload size. Expected sizes:\n{}",
                units::bits(payload.size()),
                config.payload_size);

  return payload;
}

dci_payload srsran::dci_1_1_pack(const dci_1_1_configuration& config)
{
  srsran_assert(config.payload_size.total.value() >= 12, "DCI payloads must be at least 12 bit long");

  dci_payload payload;

  // Identifier for DCI formats - 1 bit. This field is always 1, indicating a DL DCI format.
  payload.push_back(0x01U, 1);

  // Carrier indicator - 0 or 3 bits.
  if (config.carrier_indicator.has_value()) {
    payload.push_back(config.carrier_indicator.value(), config.payload_size.carrier_indicator.value());
  }

  // Bandwidth part indicator - 0, 1 or 2 bits.
  if (config.bwp_indicator.has_value()) {
    payload.push_back(config.bwp_indicator.value(), config.payload_size.bwp_indicator.value());
  }

  units::bits frequency_resource_nof_bits = config.payload_size.frequency_resource;

  if (config.dynamic_pdsch_res_allocation_type.has_value()) {
    // Indicates the DCI resource allocation type if both resource allocation type 0 and type 1 are configured.
    unsigned dynamic_alloc_type_indicator =
        config.dynamic_pdsch_res_allocation_type == dynamic_resource_allocation::type_0 ? 0 : 1;

    // The MSB bit of the frequency domain allocation field is used to indicate the resource allocation type, as per
    // TS38.212 Section 7.3.1.2.2.
    payload.push_back(dynamic_alloc_type_indicator, 1);

    // The rest of the LSB bits are used to pack the frequency domain resource allocation.
    frequency_resource_nof_bits -= units::bits(1);
  }

  // Frequency domain resource assignment - frequency_resource_nof_bits bits.
  payload.push_back(config.frequency_resource, frequency_resource_nof_bits.value());

  // Time domain resource assignment - 0, 1, 2, 3 or 4 bits.
  if (config.payload_size.time_resource != units::bits(0)) {
    payload.push_back(config.time_resource, config.payload_size.time_resource.value());
  }

  // VRB-to-PRB mapping - 0 or 1 bit.
  if (config.vrb_prb_mapping.has_value()) {
    payload.push_back(config.vrb_prb_mapping.value(), config.payload_size.vrb_prb_mapping.value());
  }

  // PRB bundling size indicator - 0 or 1 bit.
  if (config.prb_bundling_size_indicator.has_value()) {
    payload.push_back(config.prb_bundling_size_indicator.value(),
                      config.payload_size.prb_bundling_size_indicator.value());
  }

  // Rate matching indicator - 0, 1 or 2 bits.
  if (config.rate_matching_indicator.has_value()) {
    payload.push_back(config.rate_matching_indicator.value(), config.payload_size.rate_matching_indicator.value());
  }

  // ZP CSI-RS trigger - 0, 1 or 2 bits.
  if (config.zp_csi_rs_trigger.has_value()) {
    payload.push_back(config.zp_csi_rs_trigger.value(), config.payload_size.zp_csi_rs_trigger.value());
  }

  // Modulation coding scheme for TB 1 - 5 bits.
  payload.push_back(config.tb1_modulation_coding_scheme, 5);

  // New data indicator for TB 1 - 1 bit.
  payload.push_back(config.tb1_new_data_indicator, 1);

  // Redundancy version for TB 1 - 2 bits.
  payload.push_back(config.tb1_redundancy_version, 2);

  // Modulation coding scheme for TB 2 - 0 or 5 bits.
  if (config.tb2_modulation_coding_scheme.has_value()) {
    payload.push_back(config.tb2_modulation_coding_scheme.value(),
                      config.payload_size.tb2_modulation_coding_scheme.value());
  }

  // New data indicator for TB 2 - 0 or 1 bit.
  if (config.tb2_new_data_indicator.has_value()) {
    payload.push_back(config.tb2_new_data_indicator.value(), config.payload_size.tb2_new_data_indicator.value());
  }

  // Redundancy version for TB 2 - 0 or 2 bits.
  if (config.tb2_redundancy_version.has_value()) {
    payload.push_back(config.tb2_redundancy_version.value(), config.payload_size.tb2_redundancy_version.value());
  }

  // HARQ process number - 4 bits.
  payload.push_back(config.harq_process_number, 4);

  // Downlink Assignment Index (DAI) - 0, 2 or 4 bits.
  if (config.downlink_assignment_index.has_value()) {
    payload.push_back(config.downlink_assignment_index.value(), config.payload_size.downlink_assignment_index.value());
  }

  // TPC command for scheduled PUSCH - 2 bits.
  payload.push_back(config.tpc_command, 2);

  // PUCCH resource indicator - 3 bits.
  payload.push_back(config.pucch_resource_indicator, 3);

  // PDSCH to HARQ feedback timing indicator - 0, 1, 2 or 3 bits.
  if (config.pdsch_harq_fb_timing_indicator.has_value()) {
    payload.push_back(config.pdsch_harq_fb_timing_indicator.value(),
                      config.payload_size.pdsch_harq_fb_timing_indicator.value());
  }

  // Antenna ports for PDSCH transmission - 4, 5 or 6 bits.
  payload.push_back(config.antenna_ports, config.payload_size.antenna_ports.value());

  // Transmission configuration indication - 0 or 3 bits.
  if (config.tx_config_indication.has_value()) {
    payload.push_back(config.tx_config_indication.value(), config.payload_size.tx_config_indication.value());
  }

  // SRS request - 2 or 3 bits.
  payload.push_back(config.srs_request, config.payload_size.srs_request.value());

  // CBG Transmission Information (CBGTI) - 0, 2, 4, 6 or 8 bits.
  if (config.cbg_transmission_info.has_value()) {
    payload.push_back(config.cbg_transmission_info.value(), config.payload_size.cbg_transmission_info.value());
  }

  // CBG Flushing Information (CBGFI) - 0 or 1 bit.
  if (config.cbg_flushing_info.has_value()) {
    payload.push_back(config.cbg_flushing_info.value(), config.payload_size.cbg_flushing_info.value());
  }

  // DM-RS sequence initialization - 1 bit.
  payload.push_back(config.dmrs_seq_initialization, 1);

  // Padding bits, if necessary, as per TS38.212 Section 7.3.1.0.
  if (config.payload_size.padding.value() > 0) {
    payload.push_back(0x00U, config.payload_size.padding.value());
  }

  // Assert total payload size.
  srsran_assert(units::bits(payload.size()) == config.payload_size.total,
                "Constructed payload size {} does not match expected payload size. Expected sizes:\n{}",
                units::bits(payload.size()),
                config.payload_size);

  return payload;
}

dci_payload srsran::dci_rar_pack(const dci_rar_configuration& config)
{
  dci_payload payload;

  // Frequency hopping flag - 1 bit.
  payload.push_back(config.frequency_hopping_flag, 1);

  // PUSCH frequency resource allocation - 14 bits.
  payload.push_back(config.frequency_resource, 14);

  // PUSCH time resource allocation - 4 bits.
  payload.push_back(config.time_resource, 4);

  // Modulation and coding scheme - 4 bits.
  payload.push_back(config.modulation_coding_scheme, 4);

  // TPC command for PUSCH - 3 bits.
  payload.push_back(config.tpc, 3);

  // CSI request - 1 bit.
  payload.push_back(config.csi_request, 1);

  return payload;
}

error_type<std::string> srsran::validate_dci_size_config(const dci_size_config& config)
{
  // Constants.
  static constexpr unsigned max_nof_bwp_rrc         = 4;
  static constexpr unsigned max_nof_time_domain_res = 16;

  // Valid bandwidth range.
  static constexpr interval<unsigned, true> bwp_bw_range(1, MAX_NOF_PRBS);
  static constexpr interval<unsigned, true> coreset0_bw_range(0, MAX_NOF_PRBS);
  static constexpr interval<unsigned, true> nof_bwp_rrc_range(0, max_nof_bwp_rrc);
  static constexpr interval<unsigned, true> nof_time_domain_res_range(1, max_nof_time_domain_res);
  static constexpr interval<unsigned, true> non_codebook_nof_srs_res_range(1, 4);
  static constexpr interval<unsigned, true> codebook_nof_srs_res_range(1, 2);
  static constexpr interval<unsigned, true> nof_aperiodic_zp_csi_range(0, 3);
  static constexpr interval<unsigned, true> report_trigger_size_range(0, 6);
  static constexpr interval<unsigned, true> nof_pdsch_ack_timings_range(1, 8);
  static constexpr interval<unsigned, true> nof_rb_groups_range(1, MAX_NOF_RBGS);
  static constexpr interval<unsigned, true> pusch_max_layers_range(1, 4);

  // Check that UL and DL BWP and CORESET 0 bandwidths are within range.
  if (!bwp_bw_range.contains(config.dl_bwp_initial_bw)) {
    return make_unexpected(
        fmt::format("DL initial BWP bandwidth {} is out of range {}.", config.dl_bwp_initial_bw, bwp_bw_range));
  }
  if (!bwp_bw_range.contains(config.ul_bwp_initial_bw)) {
    return make_unexpected(
        fmt::format("UL initial BWP bandwidth {} is out of range {}.", config.ul_bwp_initial_bw, bwp_bw_range));
  }
  if (!coreset0_bw_range.contains(config.coreset0_bw)) {
    return make_unexpected(
        fmt::format("CORESET0 bandwidth {} is out of range {}.", config.coreset0_bw, coreset0_bw_range));
  }

  // Supplementary Uplink is not currently supported by the DCI size alignment procedure.
  if (config.sul_configured) {
    return make_unexpected(fmt::format("SUL is not currently supported by the DCI size alignment procedure."));
  }

  // Checks pertaining to any DCI format on a USS.
  if (config.dci_0_0_and_1_0_ue_ss || config.dci_0_1_and_1_1_ue_ss) {
    // DCI formats monitored on a USS need the active UL and DL BWP bandwidth.
    if (!bwp_bw_range.contains(config.dl_bwp_active_bw)) {
      return make_unexpected(
          fmt::format("DL active BWP bandwidth {} is out of range {}.", config.dl_bwp_active_bw, bwp_bw_range));
    }
    if (!bwp_bw_range.contains(config.ul_bwp_active_bw)) {
      return make_unexpected(
          fmt::format("UL active BWP bandwidth {} is out of range {}.", config.ul_bwp_active_bw, bwp_bw_range));
    }
  }

  // Checks pertaining to non-fallback DCI formats.
  if (config.dci_0_1_and_1_1_ue_ss) {
    // Number of BWP configured by higher layers cannot exceed 4.
    if (!nof_bwp_rrc_range.contains(config.nof_ul_bwp_rrc)) {
      return make_unexpected(fmt::format(
          "The number of RRC configured UL BWP {} is out of range {}.", config.nof_ul_bwp_rrc, nof_bwp_rrc_range));
    }
    if (!nof_bwp_rrc_range.contains(config.nof_dl_bwp_rrc)) {
      return make_unexpected(fmt::format(
          "The number of RRC configured DL BWP {} is out of range {}.", config.nof_dl_bwp_rrc, nof_bwp_rrc_range));
    }

    // Number of UL time domain resource allocations must be within the valid range {1, ..., 16}
    if (!nof_time_domain_res_range.contains(config.nof_ul_time_domain_res)) {
      return make_unexpected(fmt::format("The number of UL time domain resources {} is out of range {}.",
                                         config.nof_ul_time_domain_res,
                                         nof_time_domain_res_range));
    }

    // Number of DL time domain resource allocations must be within the valid range {1, ..., 16}
    if (!nof_time_domain_res_range.contains(config.nof_dl_time_domain_res)) {
      return make_unexpected(fmt::format("The number of DL time domain resources {} is out of range {}.",
                                         config.nof_dl_time_domain_res,
                                         nof_time_domain_res_range));
    }

    // Size of the DCI request field, determined by the higher layer parameter reportTriggerSize, cannot exceed 6.
    if (!report_trigger_size_range.contains(config.report_trigger_size)) {
      return make_unexpected(fmt::format(
          "CSI report trigger size {} is out of range {}.", config.report_trigger_size, report_trigger_size_range));
    }

    // Number of aperiodic ZP CSI-RS resource sets cannot exceed 3.
    if (!nof_aperiodic_zp_csi_range.contains(config.nof_aperiodic_zp_csi)) {
      return make_unexpected(fmt::format("The number of aperiodic ZP-CSI resource sets {} is out of range {}.",
                                         config.nof_aperiodic_zp_csi,
                                         nof_aperiodic_zp_csi_range));
    }

    // Number of PDSCH to DL ACK timings exceeds the valid range {1, ..., 8}.
    if (!nof_pdsch_ack_timings_range.contains(config.nof_pdsch_ack_timings)) {
      return make_unexpected(fmt::format("The number of HARQ-ACK feedback timing entries {} is out of range {}.",
                                         config.nof_pdsch_ack_timings,
                                         nof_pdsch_ack_timings_range));
    }

    // Requirements if transform precoding is enabled.
    if (config.transform_precoding_enabled) {
      // With transform precoding enabled for the UL, the PUSCH DM-RS configuration can only be type 1.
      if (config.pusch_dmrs_A_type.has_value() && (config.pusch_dmrs_A_type == dmrs_config_type::type2)) {
        return make_unexpected("PUSCH DM-RS (A) Type2 is not supported with transform precoding.");
      }
      if (config.pusch_dmrs_B_type.has_value() && (config.pusch_dmrs_B_type == dmrs_config_type::type2)) {
        return make_unexpected("PUSCH DM-RS (B) Type2 is not supported with transform precoding.");
      }
    }

    // Requirement if the PDSCH HARQ-ACK codebook type is set to dynamic.
    if (config.pdsch_harq_ack_cb == pdsch_harq_ack_codebook::dynamic) {
      // The dynamic dual HARQ-ACK codebook flag is required.
      if (!config.dynamic_dual_harq_ack_cb.has_value()) {
        return make_unexpected("Dynamic dual HARQ-ACK codebook flag is required for dynamic PDSCH HARQ-ACK codebook.");
      }
    }

    // Requirements for UL resource allocation type 0.
    if (config.pusch_res_allocation_type != resource_allocation::resource_allocation_type_1) {
      // Number of UL RBGs is required, and must not exceed the valid range.
      if (!config.nof_ul_rb_groups.has_value()) {
        return make_unexpected("The number of UL RBGs is required for resource allocation type 0.");
      }
      if (!nof_rb_groups_range.contains(config.nof_ul_rb_groups.value())) {
        return make_unexpected(
            fmt::format("The number of UL RBGs {} is out of range {}.", config.nof_ul_rb_groups, nof_rb_groups_range));
      }
    }

    // Requirements for DL resource allocation type 0.
    if (config.pdsch_res_allocation_type != resource_allocation::resource_allocation_type_1) {
      // Number of DL RBGs is required, and must not exceed the valid range.
      if (!config.nof_dl_rb_groups.has_value()) {
        return make_unexpected("The number of DL RBGs is required for resource allocation type 0.");
      }
      if (!nof_rb_groups_range.contains(config.nof_dl_rb_groups.value())) {
        return make_unexpected(
            fmt::format("The number of DL RBGs {} is out of range {}.", config.nof_dl_rb_groups, nof_rb_groups_range));
      }
    }

    // Requirements for DL resource allocation type 1.
    if (config.pdsch_res_allocation_type != resource_allocation::resource_allocation_type_0) {
      // Interleaved VRB to PRB mapping flag is required.
      if (!config.interleaved_vrb_prb_mapping.has_value()) {
        return make_unexpected("Interleaved VRB to PRB mapping flag is required for PDSCH resource allocation type 1.");
      }
    }

    // PUSCH transmit scheme is required if DCI 0_1 is enabled.
    if (config.dci_0_1_and_1_1_ue_ss && !config.pusch_tx_scheme.has_value()) {
      return make_unexpected("PUSCH Transmit scheme is required for DCI 0_1.");
    }

    // Requirements for non-codebook based transmission.
    if (config.pusch_tx_scheme.has_value() &&
        std::holds_alternative<tx_scheme_non_codebook>(config.pusch_tx_scheme.value())) {
      // PUSCH max number of layers is required.
      if (!config.pusch_max_layers) {
        return make_unexpected("Maximum number of PUSCH layers is required for non-codebook transmission.");
      }

      // PUSCH max number of layers must be within the valid range.
      if (!pusch_max_layers_range.contains(config.pusch_max_layers.value())) {
        return make_unexpected(fmt::format(
            "Maximum number of PUSCH layers {} is out of range {}.", config.pusch_max_layers, pusch_max_layers_range));
      }

      // Multiple layers on PUSCH are not currently supported.
      if (config.pusch_max_layers.value() != 1) {
        return make_unexpected("Multiple layers on PUSCH are not currently supported.");
      }

      // For non-codebook based transmission, the number of SRS resources must be within the valid range {1, ..., 4}.
      if (!non_codebook_nof_srs_res_range.contains(config.nof_srs_resources)) {
        return make_unexpected(
            fmt::format("The number of SRS resources {} is out of range {} for non-codebook transmission.",
                        config.nof_srs_resources,
                        non_codebook_nof_srs_res_range));
      }
    }

    // Requirements for codebook based transmission.
    if (config.pusch_tx_scheme.has_value() &&
        std::holds_alternative<tx_scheme_codebook>(config.pusch_tx_scheme.value())) {
      const tx_scheme_codebook& tx_scheme_cfg = std::get<tx_scheme_codebook>(config.pusch_tx_scheme.value());

      // For codebook based transmission, the number of SRS ports is required.
      if (!config.nof_srs_ports.has_value()) {
        return make_unexpected("Number of SRS ports is required for codebook transmission.");
      }

      // For codebook based transmission, the number of SRS resources must be within the valid range {1, 2}.
      if (!codebook_nof_srs_res_range.contains(config.nof_srs_resources)) {
        return make_unexpected(
            fmt::format("The number of SRS resources {} is out of range {} for codebook transmission.",
                        config.nof_srs_resources,
                        codebook_nof_srs_res_range));
      }

      // Maximum rank cannot be greater than the number of SRS ports.
      if (tx_scheme_cfg.max_rank > config.nof_srs_ports.value()) {
        return make_unexpected(fmt::format("Maximum rank {} cannot be larger than the number of SRS antenna ports {}.",
                                           tx_scheme_cfg.max_rank,
                                           config.nof_srs_ports));
      }

      // The number of SRS ports must be a valid value {1, 2, 4}.
      if ((config.nof_srs_ports.value() != 1) && (config.nof_srs_ports.value() != 2) &&
          (config.nof_srs_ports.value() != 4)) {
        return make_unexpected(fmt::format("The number of SRS ports {} is neither 1, 2, nor 4.", config.nof_srs_ports));
      }

      // Codebook subset transmission with two ports does not support partial and non-coherent.
      if ((config.nof_srs_ports.value() == 2) &&
          (tx_scheme_cfg.codebook_subset == tx_scheme_codebook_subset::partial_and_non_coherent)) {
        return make_unexpected("Codebook subset \"partial and non-coherent\" is not supported for 2 SRS ports.");
      }
    }

    // PT-RS to DM-RS association is not currently supported.
    if (config.ptrs_uplink_configured) {
      return make_unexpected("PT-RS is not currently supported.");
    }

    // At least one PUSCH DM-RS mapping must be configured.
    if ((!config.pusch_dmrs_A_type.has_value() || !config.pusch_dmrs_A_max_len.has_value()) &&
        (!config.pusch_dmrs_B_type.has_value() || !config.pusch_dmrs_B_max_len.has_value())) {
      return make_unexpected("At least one PUSCH DM-RS mapping (type A or type B) must be configured.");
    }

    // At least one PDSCH DM-RS mapping must be configured.
    if ((!config.pdsch_dmrs_A_type.has_value() || !config.pdsch_dmrs_A_max_len.has_value()) &&
        (!config.pdsch_dmrs_B_type.has_value() || !config.pdsch_dmrs_B_max_len.has_value())) {
      return make_unexpected("At least one PDSCH DM-RS mapping (type A or type B) must be configured.");
    }

    if (config.max_cbg_tb_pusch.has_value()) {
      // The Maximum PUSCH CBG per TB must be set to a valid value.
      if (!max_cbg_tb_is_valid(config.max_cbg_tb_pusch.value())) {
        return make_unexpected(
            fmt::format("The maximum CBG per PUSCH TB {} is neither 2, 4, 6, nor 8.", config.max_cbg_tb_pusch));
      }
    }

    if (config.max_cbg_tb_pdsch.has_value()) {
      // The Maximum PDSCH CBG per TB must be set to a valid value.
      if (!max_cbg_tb_is_valid(config.max_cbg_tb_pdsch.value())) {
        return make_unexpected(
            fmt::format("The maximum CBG per PDSCH TB {} is neither 2, 4, 6, nor 8.", config.max_cbg_tb_pdsch));
      }
    }
  }

  return default_success_t();
}
